﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using PhiStore.Util;

namespace PhiStore.Storage
{
    [Serializable()]
    internal class LocalStorage : IStorageDevice, ISerializable
    {
        private string basepath = "";
        private long reservedBytes = 0L;
        private long usedbytes = 0L;

        private object mutex = new object();

        private Dictionary<string, FileSystemInfo> entries;

        private string directorySeparator = Path.DirectorySeparatorChar.ToString();

        public LocalStorage(string basepath, long bytes)
        {
            this.basepath = basepath;
            if (!basepath.EndsWith(directorySeparator))
                this.basepath += directorySeparator;

            this.reservedBytes = bytes;
            lock (mutex)
            {
                BuildDictionary();
            }

        }

        private void BuildDictionary()
        {
            try
            {
                entries = new Dictionary<string, FileSystemInfo>();

                string[] paths = Directory.GetFileSystemEntries(basepath);

                foreach (string path in paths)
                {
                    if (Directory.Exists(path))
                    {
                        entries.Add(path + directorySeparator, new DirectoryInfo(path));

                        string[] subpaths = Directory.GetFileSystemEntries(path);
                        foreach (string subpath in subpaths)
                        {
                            if (File.Exists(subpath))
                            {
                                FileInfo fi = new FileInfo(subpath);
                                entries.Add(subpath, fi);
                                usedbytes += fi.Length;

                            }
                            else
                            {
                                Log.Instance.AddLogEntry(this.ToString(), "File structure broken for: " + subpath + " This should not be a directory");
                            }
                        }
                    }
                    else
                    {
                        FileInfo fi = new FileInfo(path);
                        entries.Add(path, fi);
                        usedbytes += fi.Length;
                    }

                }
            }
            catch
            {
                entries = null;
            }
        }

        private string BuildCorrectPath(string path)
        {
            return basepath + path;
        }

        public bool Write(string path, byte[] bytes)
        {
            bool result = true;
            FileStream fs = null;
            lock (mutex)
            {
                try
                {
                    string fullpath = BuildCorrectPath(path);

                    string containingDirectory = Path.GetDirectoryName(fullpath) + directorySeparator;
                    if (containingDirectory != basepath && !entries.ContainsKey(containingDirectory))
                    {
                        Directory.CreateDirectory(containingDirectory);
                        entries.Add(containingDirectory, new DirectoryInfo(containingDirectory));
                    }

                    fs = File.Open(fullpath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
                    StreamUtil.Copy(bytes, fs);

                    if (entries.ContainsKey(fullpath))
                    {

                        usedbytes += bytes.Length - (entries[fullpath] as FileInfo).Length;
                        entries[fullpath] = new FileInfo(fullpath);
                    }
                    else
                    {
                        usedbytes += bytes.Length;
                        entries.Add(fullpath, new FileInfo(fullpath));
                    }
                }
                catch
                {
                    result = false;
                }
                finally
                {
                    if (fs != null)
                        fs.Close();
                }
            }
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <returns>null if an exception occured</returns>
        public byte[] Read(string path)
        {
            byte[] result = null;
            FileStream fs = null;
            lock (mutex)
            {
                try
                {
                    fs = File.Open(BuildCorrectPath(path), FileMode.Open, FileAccess.Read, FileShare.Read);
                    result = StreamUtil.Read(fs);

                    entries[BuildCorrectPath(path)] = new FileInfo(BuildCorrectPath(path));
                }
                catch
                {
                    result = null;
                }
                finally
                {
                    if (fs != null)
                        fs.Close();
                }
            }
            return result;
        }

        public bool Delete(string path)
        {
            bool result = true;
            lock (mutex)
            {
                try
                {
                    usedbytes -= (entries[BuildCorrectPath(path)] as FileInfo).Length;

                    File.Delete(BuildCorrectPath(path));
                    entries.Remove(BuildCorrectPath(path));
                }
                catch (Exception e)
                {
                    Log.Instance.AddLogEntry("Delete", e.ToString());
                    result = false;
                }
            }
            return result;
        }

        public bool Exist(string path)
        {
            bool result = false;
            lock (mutex)
            {
                result = File.Exists(BuildCorrectPath(path)) || Directory.Exists(BuildCorrectPath(path));
            }
            return result;
        }

        public bool Rename(string oldpath, string newpath)
        {
            bool result = true;
            lock (mutex)
            {
                if (oldpath != newpath)
                {
                    try
                    {
                        string cOldPath = BuildCorrectPath(oldpath);
                        string cNewPath = BuildCorrectPath(newpath);

                        if (Directory.Exists(cNewPath))
                        {
                            Directory.Delete(cNewPath, true);
                        }
                        if (File.Exists(cNewPath))
                        {
                            File.Delete(cNewPath);
                        }

                        if (entries[cOldPath] is DirectoryInfo)
                        {
                            string[] paths = Directory.GetFiles(cOldPath);

                            Directory.Move(cOldPath, cNewPath);
                            entries.Remove(cOldPath);
                            entries.Add(cNewPath, new DirectoryInfo(cNewPath));

                            foreach (string path in paths)
                            {
                                entries.Remove(path);
                                string newSubPath = path.Replace(oldpath, newpath);
                                entries.Add(newSubPath, new FileInfo(newSubPath));
                            }
                        }
                        else
                        {
                            File.Move(BuildCorrectPath(oldpath), BuildCorrectPath(newpath));
                            entries.Remove(BuildCorrectPath(oldpath));
                            entries.Add(BuildCorrectPath(newpath), new FileInfo(BuildCorrectPath(newpath)));
                        }
                    }
                    catch (Exception e)
                    {
                        Log.Instance.AddLogEntry("Rename", e.ToString());
                        result = false;
                    }
                }
            }
            return result;
        }

        public List<string> GetPaths()
        {
            List<string> paths = null;
            lock (mutex)
            {
                try
                {
                    paths = new List<string>();
                    foreach (string path in entries.Keys)
                    {
                        paths.Add(path.Remove(0, basepath.Length));
                    }
                }
                catch
                {
                    paths = null;
                }
            }
            return paths;
        }

        public bool Works()
        {
            bool result = false;
            lock (mutex)
            {
                result = Directory.Exists(basepath);
            }
            return result;
        }
        public override string ToString()
        {
            return BasePath;
        }

        public string BasePath
        {
            get
            {
                string result = "";
                lock (mutex)
                {
                    result = basepath;
                }
                return result; ;
            }
        }

        public string DirectorySeperator
        {
            get
            {
                string result = "";
                lock (mutex)
                {
                    result = directorySeparator;
                }
                return result;
            }
        }

        public long RemainingBytes
        {
            get
            {
                long result = 0L;
                lock (mutex)
                {
                    result = this.reservedBytes - this.usedbytes;
                }
                return result;
            }
        }

        public long ReservedBytes
        {
            get
            {
                long result = 0L;
                lock (mutex)
                {
                    result = this.reservedBytes;
                }
                return result;
            }
        }

        #region serialization
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("basepath", basepath);
            info.AddValue("reservedBytes", reservedBytes);
        }

        public LocalStorage(SerializationInfo info, StreamingContext context)
        {
            this.basepath = info.GetString("basepath");
            this.reservedBytes = info.GetInt64("reservedBytes");
            lock (mutex)
            {
                BuildDictionary();
            }
        }
        #endregion

        public bool DeleteDirectory(string path)
        {
            string dirPath = BuildCorrectPath(path);
            bool result = true;
            lock (mutex)
            {
                try
                {
                    if (entries.ContainsKey(dirPath))
                    {
                        string[] paths = Directory.GetFiles(dirPath);

                        foreach (string p in paths)
                        {
                            if (entries.ContainsKey(p))
                            {

                                usedbytes -= (entries[p] as FileInfo).Length;

                                entries.Remove(p);
                            }
                        }

                        Directory.Delete(dirPath, true);
                        entries.Remove(dirPath);

                    }
                }
                catch (Exception e)
                {
                    Log.Instance.AddLogEntry("Rename", e.ToString());
                    result = false;
                }
            }
            return result;

        }

        public bool Create(string path)
        {
            bool result = false;
            lock (mutex)
            {
                if (!Exist(path))
                {
                    try
                    {
                        Directory.CreateDirectory(BuildCorrectPath(path));
                        result = true;
                    }
                    catch
                    {
                        result = false;
                    }
                }
            }
            return result;
        }


        public DateTime LastAccess(string path)
        {
            DateTime result = new DateTime(1901, 1, 1); //this is oooold, as in before the kapewter was invented
            lock (mutex)
            {
                string cpath = BuildCorrectPath(path);
                foreach (KeyValuePair<string, FileSystemInfo> entry in entries)
                {
                    if (entry.Key.StartsWith(cpath))
                    {
                        if (entry.Value.LastAccessTime > result)
                            result = entry.Value.LastAccessTime;
                    }
                }
            }
            return result;
        }

    }
}
